﻿using System;
using System.Data;
using System.Text;
using System.Collections;

namespace BizElements.Core
{
    #region Documentation.
    /// <summary>
    /// Specifies the groups into which output rows of a <see cref="SelectStatement"/> are to be placed.
    /// </summary>
    /// <remarks><para>The GROUP BY clause is used to produce aggregate values for each row in the result set. 
    /// The GROUP BY clause restricts the rows of the result set; there is only one row for each distinct 
    /// value in the grouping column or columns. Each result set row contains summary data related to the 
    /// specific value in its grouping columns.</para>
    /// <para><b>Restrictions.</b> Some RDBMSs don't support GROUP BY clause for all field types. Eg. the text, 
    /// ntext, or image data type cannot be used in a GROUP BY clause on SQL Server 7 and 2000.</para></remarks>
    /// <example>
    /// The following example gets the number of territories each employee is in charge of.
    /// <code>
    /// public DataTable GetNumberOfTerritoriesForEachEmployee()
    /// {	
    /// 	// The equivalent SQL code that does the same task.
    /// 
    /// 	// SELECT EmployeeTerritories.EmployeeID, COUNT(*) AS numOfTerritories 
    /// 	// FROM EmployeeTerritories 
    /// 	// GROUP BY EmployeeTerritories.EmployeeID
    /// 
    /// 	EmployeeTerritoriesMeta empTerritories = new EmployeeTerritoriesMeta();
    /// 
    /// 	SelectStatement select = new SelectStatement(empTerritories);
    /// 	select.SelectList.Add(empTerritories.EmployeeID);
    /// 	select.SelectList.Add(AggregateFunctionFactory.Count("numOfTerritories"));
    /// 	select.GroupBy.Fields.Add(empTerritories.EmployeeID);
    /// 
    /// 	return select.Execute();
    /// }
    /// </code>
    /// </example>
    #endregion
    [Serializable]
    public sealed class GroupByClause : IRenderSql
    {
        #region Members.

        DbColumnCollection groupingColumns;

        #endregion

        #region Constructors.

        /// <summary>
        /// Initializes a new instance of the GroupByClause class.
        /// </summary>
        public GroupByClause()
        {
            this.groupingColumns = new DbColumnCollection();
        }

        /// <summary>
        /// Initializes a new instance of the GroupByClause class.
        /// </summary>
        /// <param name="groupingColumns">Grouping columns.</param>
        public GroupByClause(DbColumnCollection groupingColumns)
        {
            this.groupingColumns = groupingColumns; ;
        }

        /// <summary>
        /// Initializes a new instance of the GroupByClause class.
        /// </summary>
        /// <param name="selectList">Select list from which the grouping columns are extracted.</param>
        /// <remarks>Very often a GROUP BY clause contains all the columns in the select list. 
        /// This method adds all columns found in the given select list to the GROUP BY clause. 
        /// Other select list items such as aggregate functions are skipped.</remarks>
        public GroupByClause(SelectItemCollection selectList)
        {
            SelectItemCollection columns = selectList.GetByType(SqlItemType.Column);
            this.groupingColumns = new DbColumnCollection();
            foreach (ISqlItem columnItem in columns)
                this.groupingColumns.Add((IDbColumn)columnItem.Item);
        }

        #endregion

        #region Methods.

        /// <summary>
        /// Renders GROUP BY statement.
        /// </summary>
        /// <param name="dbms">Target DBMS.</param>
        /// <param name="output">StringBuilder to which SQL is appended.</param>
        /// <param name="parameters">SQL parameter collection to which the object's and its children's
        /// parameters are added. After the rendering is done the collection contains all parameters with unique names.</param>
        public void Render(DbmsType dbms, StringBuilder output, DbParameterCollection parameters)
        {
            int countOfFields = this.groupingColumns.Count;
            if (countOfFields > 0)
            {
                output.Append("GROUP BY ");
                for (int colIdx = 0; colIdx < countOfFields - 1; colIdx++)
                {
                    this.groupingColumns[colIdx].RenderFullName(dbms, output);
                    output.Append(", ");
                }
                this.groupingColumns[countOfFields - 1].RenderFullName(dbms, output);
            }
        }

        #endregion

        #region Properties.

        /// <summary>
        /// Gets the grouping columns of this GROUP BY clause.
        /// </summary>
        public DbColumnCollection Fields
        {
            get { return this.groupingColumns; }
        }

        #endregion
    }
}